
/*
 * A condition variable is a variable of type 'pthread_cond_t' and is used 
 * with the appropriate functions for waiting and later, process continuation. 
 * The condition variable mechanism allows threads to suspend execution and 
 * relinquish the processor until some condition is true. 
 * A condition variable must always be associated with a mutex to avoid 
 * a race condition created by one thread preparing to wait and another thread 
 * which may signal the condition before the first thread actually waits on it 
 * resulting in a deadlock. The thread will be perpetually waiting for 
 * a signal that is never sent. Any mutex can be used, there is no explicit 
 * link between the mutex and the condition variable. 
*/



#include <iostream>
#include <pthread.h>


using namespace std;


// Global variables and constants
// ------------------------------

pthread_mutex_t myLockMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t myConditionVariable = PTHREAD_COND_INITIALIZER;

int myCounter = 0;

static const int COUNT_DONE = 10;
static const int COUNT_HALT_1 = 3;
static const int COUNT_HALT_2 = 6;


// Function prototypes
// -------------------

void* tread1Function(void*);
void* tread2Function(void*);



// Main function (creates the threads, waits until all finished (joins) and returns)
// -------------

int main (int argc, char **argv)
{
    pthread_t thread1, thread2;
    
    
    // Argument description of pthread_create
    // 1. pointer to thread variable itself
    // 2. attributes of the thread
    // 3. start function of the thread
    // 4. argument given to the start function (to pass multiple arguments, use a pointer to a structure)
        
    pthread_create(&thread1, NULL, &tread1Function, NULL);
    pthread_create(&thread2, NULL, &tread2Function, NULL);
 
 
    // Argument description of pthread_join
    // 1. pointer to the thread variable itself (wait on this threds terminates)
    // 2. The return value of the start function will be stored at this location
 
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    
    // Print out the final value of the global variable "myCounter"...
    
    cout << endl << "Final counter value: " << dec << myCounter << endl;
    
    return 0;
}


// Implementation of the "thread functionalities"
// ----------------------------------------------

void* tread1Function(void*)
{
    while(1)
    {
        pthread_mutex_lock(&myLockMutex);
        
            // Wait while tread2Function() operates on count
            // The mutex is unlocked if condition varialbe 
            // in tread2Function() signaled.
            
            pthread_cond_wait(&myConditionVariable, &myLockMutex);
            
            myCounter++;
            cout << "Thread id: " << hex << pthread_self() << ", increments counter: " << dec << myCounter << endl;
        
        pthread_mutex_unlock(&myLockMutex);
        
        if (myCounter >= COUNT_DONE)
        {
            return NULL;
        }
    }
}


void* tread2Function(void* theDummyArgument)
{
    while(1)
    {
        pthread_mutex_lock(&myLockMutex);
        
            if (myCounter < COUNT_HALT_1 || myCounter > COUNT_HALT_2)
            {
                // Condition of if statement has been met.
                // Signal to free waiting thread by freeing the mutex.
                // Note: tread1Function() is now permitted to modify 'myCounter'.
                
                pthread_cond_signal(&myConditionVariable);
            }
            else
            {
                myCounter++;
                cout << "Thread id: " << hex << pthread_self() << ", increments counter: " << dec << myCounter << endl;
            }
        
        pthread_mutex_unlock(&myLockMutex);
        
        if (myCounter >= COUNT_DONE)
        {
            return NULL;
        }
    }
}

